﻿using System;
using System.Collections.Generic;
using SeaBattle.Domain.Abstract;
using SeaBattle.Domain.Concrete;
using System.Linq;

namespace SeaBattle.Domain.Entities
{
    public class Game
    {
        private IShipRepository shipRepository;
        private IFleetRepository fleetRepository;
        private IPlayerRepository playerRepository;

        /// <summary>
        /// Initializes a new instance of the <see cref="Game"/> class.
        /// </summary>
        /// <param name="id">The id.</param>
        /// <param name="created">The created.</param>
        /// <param name="creator">The creator.</param>
        /// <param name="shipRepository">The ship repository.</param>
        /// <param name="fleetRepository">The fleet repository.</param>
        /// <param name="playerRepository">The player repository.</param>
        /// <remarks></remarks>
        public Game(int id, DateTime created, Player creator, IShipRepository shipRepository, 
            IFleetRepository fleetRepository, IPlayerRepository playerRepository)
        {
            Id = id;
            Created = created;
            Creator = creator;
            State = GameState.STATE_CREATED;
            this.shipRepository = shipRepository;
            this.fleetRepository = fleetRepository;
            this.playerRepository = playerRepository;           
        }

        /// <summary>
        /// Initializes the fleet.
        /// </summary>
        /// <param name="player">The player.</param>
        /// <remarks></remarks>
        private void InitializeFleet(Player player)
        {

        }

        private Player opponent;

        /// <summary>
        /// Gets the id.
        /// </summary>
        /// <remarks></remarks>
        public int Id { get; private set; }
        
        /// <summary>
        /// Gets or sets the preset.
        /// </summary>
        /// <value>The preset.</value>
        /// <remarks></remarks>
        public int Preset { get; set; }
        
        /// <summary>
        /// Gets the created.
        /// </summary>
        /// <remarks></remarks>
        public DateTime Created { get; private set; }
        
        /// <summary>
        /// Gets the creator.
        /// </summary>
        /// <remarks></remarks>
        public Player Creator { get; private set; }
        
        /// <summary>
        /// Gets or sets the opponent.
        /// </summary>
        /// <value>The opponent.</value>
        /// <remarks></remarks>
        public Player Opponent { get { return opponent; } set{SetOpponent(value);} }

        private Player winner;
        public Player Winner 
        {
            get
            {
                if (State == GameState.STATE_CLOSED)
                    return winner;
                return null;
            }
            set {
                winner = value;
            }
        }

        /// <summary>
        /// Gets the state.
        /// </summary>
        /// <remarks></remarks>
        public GameState State { get; private set; }

        /// <summary>
        /// Sets the opponent.
        /// </summary>
        /// <param name="theOpponent">The opponent.</param>
        /// <remarks></remarks>
        private void SetOpponent(Player theOpponent)
        {
            // The game must be in Opened status
            if(!Opened)
            {
                throw new Exception("The game "+Id+" not opened");
            }
            // Check whether Creator and Opponent are not the same persons
            if(Creator == theOpponent)
            {
                throw new Exception("The opponent is the same as creator of the game "+Id);
            }
            // Set opponent
            opponent = theOpponent;
        }

        /// <summary>
        /// Joins the opponent.
        /// </summary>
        /// <param name="theOpponent">The opponent.</param>
        /// <remarks></remarks>
        public void JoinOpponent(Player theOpponent)
        {
            Opponent = theOpponent;
            BoardPreparing = true;
            {//DEMO
                realField.Add(Creator, new Field(10));
                realField.Add(theOpponent, new Field(10));
                virtualField.Add(Creator, new Field(10));
                virtualField.Add(theOpponent, new Field(10));
                Checking(Creator);
                Checking(Opponent);
            }
        }

        /// <summary>
        /// Gets the max part count.
        /// </summary>
        /// <remarks></remarks>
        public byte MaxPartCount { get;private set; }
        
        /// <summary>
        /// Gets the max ship count.
        /// </summary>
        /// <remarks></remarks>
        public byte MaxShipCount { get; private set; }

        /// <summary>
        /// Games the setuping.
        /// </summary>
        /// <param name="preset">The preset.</param>
        /// <param name="maxShipCount">The max ship count.</param>
        /// <remarks></remarks>
        public void GameSetuping(int preset, byte maxShipCount)
        {
            if (State == GameState.STATE_GAME_SETUP)
            {
                Preset = preset;
                checked { MaxPartCount = (byte) (preset / 20); }
                MaxShipCount = maxShipCount;
            }
            else
                throw new Exception("You can not game setuping in current state: " + State.ToString());
        }
        
        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="Game"/> is opened.
        /// </summary>
        /// <value><c>true</c> if opened; otherwise, <c>false</c>.</value>
        /// <remarks></remarks>
        public bool Opened
        {
            get { return State == GameState.STATE_OPENED; }
            set
            {            
                // Check 
                // Nop
                // Change real state
                if (value)
                {
                    State = GameState.STATE_OPENED;
                }
            }
        }
        
        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="Game"/> is closed.
        /// </summary>
        /// <value><c>true</c> if closed; otherwise, <c>false</c>.</value>
        /// <remarks></remarks>
        public bool Closed
        {
            get { return State == GameState.STATE_CLOSED; }
            set
            {
                // Check 
                // Nop
                // Change real state
                if (value)
                {
                    State = GameState.STATE_CLOSED;
                }
            }
        }

        public void GameClosing(Player winner)
        {
            State = GameState.STATE_CLOSED;
            Winner = winner;
        }

        public Dictionary<Player, Field> realField = new Dictionary<Player, Field>();
        public Dictionary<Player, Field> virtualField = new Dictionary<Player, Field>();
        public Dictionary<Player, int> stepMakingSheep = new Dictionary<Player, int>(); 

        public Dictionary<Player, BoardSetupState> BoardStates = new Dictionary<Player, BoardSetupState>();

        /// <summary>
        /// Gets or sets a value indicating whether [board preparing].
        /// </summary>
        /// <value><c>true</c> if [board preparing]; otherwise, <c>false</c>.</value>
        /// <remarks></remarks>
        /// 
        public bool BoardPreparing
        {
            get { return State == GameState.STATE_BOARD_SETUP; }
            set
            {
                // Check 
                // Nop
                // Change real state
                if (value)
                {
                    State = GameState.STATE_BOARD_SETUP;
                    BoardStates.Add(Creator, BoardSetupState.STATEONE1);
                    BoardStates.Add(Opponent, BoardSetupState.STATEONE1);
                    stepMakingSheep.Add(Creator, 0);
                    stepMakingSheep.Add(Opponent, 0);
                    navies.Add(Creator,fleetRepository.CreateFleet(MaxShipCount,MaxPartCount));
                    navies.Add(Opponent, fleetRepository.CreateFleet(MaxShipCount, MaxPartCount));
                }
            }
        }

        private Dictionary<Player, Fleet> navies = new Dictionary<Player,Fleet>();

        /// <summary>
        /// Gets the creator fleet.
        /// </summary>
        /// <remarks></remarks>
        public Fleet CreatorFleet 
        {
            get
            {
                return navies[Creator];
            }
        }

        /// <summary>
        /// Gets the opponent fleet.
        /// </summary>
        /// <remarks></remarks>
        public Fleet OpponentFleet
        {
            get
            {
                return navies[Opponent];
            }
        }

        /// <summary>
        /// Adds the ship.
        /// </summary>
        /// <param name="player">The player.</param>
        /// <param name="X">The X.</param>
        /// <param name="Y">The Y.</param>
        /// <param name="partsCount">The parts count.</param>
        /// <param name="orientation">The orientation.</param>
        /// <returns></returns>
        /// <remarks></remarks>
        public Ship AddShip(Player player, byte X, byte Y, byte partsCount, ShipOrientation orientation)
        {
            if (State == GameState.STATE_BOARD_SETUP)
            {
                var ship = shipRepository.CreateShip(partsCount, X, Y);
                if (player == Creator || player == Opponent)
                {
                    navies[player].AddShip(ship, orientation);
                }
                return ship;
            }
            throw new Exception("You can not create new ship in this game state: " + State.ToString());
        }

        /// <summary>
        /// Removes the ship.
        /// </summary>
        /// <param name="id">The id.</param>
        /// <remarks></remarks>
        public void RemoveShip(int id)
        {
            var ship = shipRepository.GetShip(id);
            navies[Creator].RemoveShip(ship);
            navies[Opponent].RemoveShip(ship);
        }

        /// <summary>
        /// Resets the fleet.
        /// </summary>
        /// <param name="login">The login.</param>
        /// <remarks></remarks>
        public void ResetFleet(string login)
        {
            var player = playerRepository.GetPlayer(login);
            navies[player].ResetFleet();
        }

        public bool Checking(Player player)
        {
            if (BoardStates[player] >= BoardSetupState.STATEONE1 && BoardStates[player] <= BoardSetupState.STATEONE4)
                realField[player].ChekingOne();
            else if (BoardStates[player] >= BoardSetupState.STATETWO1 && BoardStates[player] <= BoardSetupState.STATETWO3)
                realField[player].ChekingTwo(stepMakingSheep[player]);
            else if (BoardStates[player] >= BoardSetupState.STATETHREE1 && BoardStates[player] <= BoardSetupState.STATETHREE2)
            {
                realField[player].ChekingThree(stepMakingSheep[player]);
            }
            else if (BoardStates[player] == BoardSetupState.STATEFOUR1)
            {
                realField[player].ChekingFour(stepMakingSheep[player]);
            }
            else return true;
            return false;
        }

        public bool Setting(Player player,int x, int y)
        {
            bool b = false;
            bool ret = false;
            if (BoardStates[player] >= BoardSetupState.STATEONE1 && BoardStates[player] <= BoardSetupState.STATEONE4)
            {
                b = realField[player].SettingOne(x, y);
                BoardStates[player]++;
                ret = Checking(player);
            }
            else if (BoardStates[player] >= BoardSetupState.STATETWO1 && BoardStates[player] <= BoardSetupState.STATETWO3)
            {
                b = realField[player].SettingTwo(x, y,stepMakingSheep[player]);
                stepMakingSheep[player]++;
                if (b)
                {
                    stepMakingSheep[player] = 0;
                    BoardStates[player]++;
                }
                ret = Checking(player);
            }
            else if (BoardStates[player] >= BoardSetupState.STATETHREE1 && BoardStates[player] <= BoardSetupState.STATETHREE2)
            {
                b = realField[player].SettingThree(x, y, stepMakingSheep[player]);
                stepMakingSheep[player]++;
                if (b)
                {
                    stepMakingSheep[player] = 0;
                    BoardStates[player]++;
                }
                ret = Checking(player);
            }
            else if (BoardStates[player] == BoardSetupState.STATEFOUR1)
            {
                b = realField[player].SettingFour(x, y, stepMakingSheep[player]);
                stepMakingSheep[player]++;
                if (b)
                {
                    stepMakingSheep[player] = 0;
                    BoardStates[player]++;
                }
                ret = Checking(player);
            }
            return ret;
        }

        public void Start()
        {
            if (BoardStates[Creator] == BoardSetupState.STATEEND &&
                BoardStates[Opponent] == BoardSetupState.STATEEND)
            {
                State = GameState.STATE_PLAY;
                Step = Creator;
            }
        }

        public void ResetField(Player player)
        {
            if (State != GameState.STATE_BOARD_SETUP)
                throw new Exception("Please don't hack =) Don't Reset field");
            realField[player].ResetField();
            stepMakingSheep[player] = 0;
            BoardStates[player] = BoardSetupState.STATEONE1;
            Checking(player);
        }

        public Player Step;

        public bool Shot(Player player, int x, int y)
        {
            if (player != Step)
                throw new Exception("Please dont hack, wait for your step");
            var opponent = player == Creator ? Opponent : Creator;
            bool isKill=false, isEnd=false;
            bool ret = realField[opponent].Shot(virtualField[player], x, y);
            if (ret)
            {
                isKill = realField[opponent].IsKill(x, y, null);
                if (isKill)
                {
                    realField[opponent].SetPlayNoAcsess(virtualField[player],x,y);
                    isEnd = realField[opponent].IsEnd();
                }
                if (isEnd)
                {
                    ClosingGame(player);
                }
            }
            else
            {
                Step = opponent;
            }
            return isEnd;
        }

        public bool isEnd
        {
            get
            {
                if (State == GameState.STATE_CLOSED)
                    return true;
                return false;
            }
        }

        private void ClosingGame(Player winner)
        {
            State = GameState.STATE_CLOSED;
            this.winner = winner;
            ChangePreset = false;
        }

        public bool ChangePreset { get; set; }
    }

    public enum GameState
    {        
        STATE_CREATED = 0,
        STATE_GAME_SETUP,
        STATE_OPENED,
        STATE_BOARD_SETUP, // Setup game board
        STATE_PLAY,
        STATE_PAUSE,
        STATE_CLOSED
    }

    public enum BoardSetupState
    {
        STATEONE1 = 0,
        STATEONE2 = 1,
        STATEONE3 = 2,
        STATEONE4 = 3,
        STATETWO1,
        STATETWO2,
        STATETWO3,
        STATETHREE1,
        STATETHREE2,
        STATEFOUR1,
        STATEEND
    }
}

